
// d_net.c
// This version has the fixed ticdup code
//
// $Revision: 470 $
// $Date: 2009-05-26 13:47:25 +0300 (Tue, 26 May 2009) $

#include "h2stdinc.h"
#include "doomdef.h"

#define NCMD_EXIT	0x80000000
#define NCMD_RETRANSMIT	0x40000000
#define NCMD_SETUP	0x20000000
#define NCMD_KILL	0x10000000		/* kill game */
#define NCMD_CHECKSUM	0x0fffffff


doomcom_t		*doomcom;
doomdata_t		*netbuffer;		/* points inside doomcom */


/*
==============================================================================

							NETWORKING

gametic is the tic about to (or currently being) run
maketic is the tick that hasn't had control made for it yet
nettics[] has the maketics for all players

a gametic cannot be run until nettics[] > gametic for all players

==============================================================================
*/

#define RESENDCOUNT	10
#define PL_DRONE	0x80			/* bit flag in doomdata->player */

ticcmd_t	localcmds[BACKUPTICS];

ticcmd_t	netcmds[MAXPLAYERS][BACKUPTICS];
int		nettics[MAXNETNODES];

int		maketic;
int		lastnettic, skiptics;
int		ticdup;
int		maxsend;			/* BACKUPTICS/(2*ticdup)-1 */

static boolean	nodeingame[MAXNETNODES];	/* set false as nodes leave game */
static boolean	remoteresend[MAXNETNODES];	/* set when local needs tics */
static int	resendto[MAXNETNODES];		/* set when remote needs tics */
static int	resendcount[MAXNETNODES];

static int	nodeforplayer[MAXPLAYERS];

static boolean	reboundpacket;
static doomdata_t	reboundstore;

/* extern functions */
void D_ProcessEvents (void);
void G_BuildTiccmd (ticcmd_t *cmd);
void D_DoAdvanceDemo (void);


//============================================================================


static int NetbufferSize (void)
{
	return (int) ((ptrdiff_t)&(((doomdata_t *)0)->cmds[netbuffer->numtics]));
}

static unsigned int NetbufferChecksum (void)
{
	unsigned int	c;
	int		i, l;

	c = 0x1234567;

#if defined(NeXT) || defined(NORMALUNIX)
	return 0;	/* byte order problems */
#endif

	l = NetbufferSize() - (int)((ptrdiff_t)&(((doomdata_t *)0)->retransmitfrom))/4;
	for (i = 0; i < l; i++)
		c += ((unsigned int *)&netbuffer->retransmitfrom)[i] * (i + 1);

	return c & NCMD_CHECKSUM;
}

static int ExpandTics (int low)
{
	int	delta;

	delta = low - (maketic & 0xff);

	if (delta >= -64 && delta <= 64)
		return (maketic & ~0xff) + low;
	if (delta > 64)
		return (maketic & ~0xff) - 256 + low;
	if (delta < -64)
		return (maketic & ~0xff) + 256 + low;

	I_Error ("ExpandTics: strange value %i at maketic %i", low, maketic);
	return 0;
}


//============================================================================


/*
==============
=
= HSendPacket
=
==============
*/

static void HSendPacket (int node, int flags)
{
	netbuffer->checksum = NetbufferChecksum () | flags;

	if (!node)
	{
		reboundstore = *netbuffer;
		reboundpacket = true;
		return;
	}

	if (demoplayback)
		return;

	if (!netgame)
		I_Error ("Tried to transmit to another node");

	doomcom->command = CMD_SEND;
	doomcom->remotenode = node;
	doomcom->datalength = NetbufferSize();

	if (debugfile)
	{
		int		i;
		int		realretrans;

		if (netbuffer->checksum & NCMD_RETRANSMIT)
			realretrans = ExpandTics (netbuffer->retransmitfrom);
		else
			realretrans = -1;

		fprintf (debugfile, "send (%i + %i, R %i) [%i] ",
			 ExpandTics(netbuffer->starttic), netbuffer->numtics,
			 realretrans, doomcom->datalength);

		for (i = 0; i < doomcom->datalength; i++)
			fprintf (debugfile, "%i ", ((byte *)netbuffer)[i]);

		fprintf (debugfile, "\n");
	}

	I_NetCmd ();
}

/*
==============
=
= HGetPacket
=
= Returns false if no packet is waiting
=
==============
*/

static boolean HGetPacket (void)
{
	if (reboundpacket)
	{
		*netbuffer = reboundstore;
		doomcom->remotenode = 0;
		reboundpacket = false;
		return true;
	}

	if (!netgame)
		return false;
	if (demoplayback)
		return false;

	doomcom->command = CMD_GET;
	I_NetCmd ();
	if (doomcom->remotenode == -1)
		return false;

	if (doomcom->datalength != NetbufferSize())
	{
		if (debugfile)
			fprintf (debugfile, "bad packet length %i\n", doomcom->datalength);
		return false;
	}

	if (NetbufferChecksum () != (netbuffer->checksum & NCMD_CHECKSUM))
	{
		if (debugfile)
			fprintf (debugfile, "bad packet checksum\n");
		return false;
	}

	if (debugfile)
	{
		int		realretrans;
		int		i;

		if (netbuffer->checksum & NCMD_SETUP)
			fprintf (debugfile, "setup packet\n");
		else
		{
			if (netbuffer->checksum & NCMD_RETRANSMIT)
				realretrans = ExpandTics (netbuffer->retransmitfrom);
			else
				realretrans = -1;

			fprintf (debugfile, "get %i = (%i + %i, R %i)[%i] ",
				 doomcom->remotenode,  ExpandTics(netbuffer->starttic),
				 netbuffer->numtics, realretrans, doomcom->datalength);

			for (i = 0; i < doomcom->datalength; i++)
				fprintf (debugfile, "%i ", ((byte *)netbuffer)[i]);

			fprintf (debugfile, "\n");
		}
	}
	return true;
}


/*
===================
=
= GetPackets
=
===================
*/

static char	exitmsg[80];

static void GetPackets (void)
{
	int		netconsole;
	int		netnode;
	ticcmd_t	*src, *dest;
	int		realend;
	int		realstart;

	while (HGetPacket ())
	{
		if (netbuffer->checksum & NCMD_SETUP)
			continue;	// extra setup packet

		netconsole = netbuffer->player & ~PL_DRONE;
		netnode = doomcom->remotenode;
		//
		// to save bytes, only the low byte of tic numbers are sent
		// Figure out what the rest of the bytes are
		//
		realstart = ExpandTics (netbuffer->starttic);
		realend = (realstart + netbuffer->numtics);

		//
		// check for exiting the game
		//
		if (netbuffer->checksum & NCMD_EXIT)
		{
			if (!nodeingame[netnode])
				continue;
			nodeingame[netnode] = false;
			playeringame[netconsole] = false;
			strcpy (exitmsg, "PLAYER 1 LEFT THE GAME");
			exitmsg[7] += netconsole;
			players[consoleplayer].message = exitmsg;
		//	if (demorecording)
		//		G_CheckDemoStatus ();
			continue;
		}

		//
		// check for a remote game kill
		//
		if (netbuffer->checksum & NCMD_KILL)
			I_Error ("Killed by network driver");

		nodeforplayer[netconsole] = netnode;

		//
		// check for retransmit request
		//
		if (resendcount[netnode] <= 0 && (netbuffer->checksum & NCMD_RETRANSMIT))
		{
			resendto[netnode] = ExpandTics(netbuffer->retransmitfrom);
			if (debugfile)
				fprintf (debugfile, "retransmit from %i\n", resendto[netnode]);
			resendcount[netnode] = RESENDCOUNT;
		}
		else
			resendcount[netnode]--;

		//
		// check for out of order / duplicated packet
		//
		if (realend == nettics[netnode])
			continue;

		if (realend < nettics[netnode])
		{
			if (debugfile)
				fprintf (debugfile, "out of order packet (%i + %i)\n", realstart, netbuffer->numtics);
			continue;
		}

		//
		// check for a missed packet
		//
		if (realstart > nettics[netnode])
		{
		// stop processing until the other system resends the missed tics
			if (debugfile)
				fprintf (debugfile, "missed tics from %i (%i - %i)\n", netnode, realstart, nettics[netnode]);
			remoteresend[netnode] = true;
			continue;
		}

		//
		// update command store from the packet
		//
		{
			int		start;

			remoteresend[netnode] = false;

			start = nettics[netnode] - realstart;
			src = &netbuffer->cmds[start];

			while (nettics[netnode] < realend)
			{
				dest = &netcmds[netconsole][nettics[netnode] % BACKUPTICS];
				nettics[netnode]++;
				*dest = *src;
				src++;
			}
		}
	}
}

/*
=============
=
= NetUpdate
=
= Builds ticcmds for console player
= sends out a packet
=============
*/

static int	gametime;

void NetUpdate (void)
{
	int		nowtime;
	int		newtics;
	int		i, j;
	int		realstart;
	int		gameticdiv;

//
// check time
//
	nowtime = I_GetTime()/ticdup;
	newtics = nowtime - gametime;
	gametime = nowtime;

	if (newtics <= 0)		// nothing new to update
		goto listen;

	if (skiptics <= newtics)
	{
		newtics -= skiptics;
		skiptics = 0;
	}
	else
	{
		skiptics -= newtics;
		newtics = 0;
	}

	netbuffer->player = consoleplayer;

//
// build new ticcmds for console player
//
	gameticdiv = gametic/ticdup;
	for (i = 0; i < newtics; i++)
	{
		I_StartTic ();
		D_ProcessEvents ();
		if (maketic - gameticdiv >= BACKUPTICS/2 - 1)
			break;		// can't hold any more
	//	printf ("mk:%i ", maketic);
		G_BuildTiccmd (&localcmds[maketic % BACKUPTICS]);
		maketic++;
	}

	if (singletics)
		return;		// singletic update is syncronous

//
// send the packet to the other nodes
//
	for (i = 0; i < doomcom->numnodes; i++)
	{
		if (nodeingame[i])
		{
			netbuffer->starttic = realstart = resendto[i];
			netbuffer->numtics = maketic - realstart;
			if (netbuffer->numtics > BACKUPTICS)
				I_Error ("NetUpdate: netbuffer->numtics > BACKUPTICS");

			resendto[i] = maketic - doomcom->extratics;

			for (j = 0; j < netbuffer->numtics; j++)
				netbuffer->cmds[j] = localcmds[(realstart + j) % BACKUPTICS];

			if (remoteresend[i])
			{
				netbuffer->retransmitfrom = nettics[i];
				HSendPacket (i, NCMD_RETRANSMIT);
			}
			else
			{
				netbuffer->retransmitfrom = 0;
				HSendPacket (i, 0);
			}
		}
	}

//
// listen for other packets
//
listen:

	GetPackets ();
}


/*
=====================
=
= CheckAbort
=
=====================
*/

static void CheckAbort (void)
{
	event_t	*ev;
	int		stoptic;

	stoptic = I_GetTime () + 2;
	while (I_GetTime() < stoptic)
		I_StartTic ();

	I_StartTic ();
	while (eventtail != eventhead)
	{
		ev = &events[eventtail];
		if (ev->type == ev_keydown && ev->data1 == KEY_ESCAPE)
			I_Error ("Network game synchronization aborted.");
		eventtail = (eventtail + 1) & (MAXEVENTS - 1);
	}
}

/*
=====================
=
= D_ArbitrateNetStart
=
=====================
*/

static void D_ArbitrateNetStart (void)
{
	int		i;
	boolean	gotinfo[MAXNETNODES];

	autostart = true;
	memset (gotinfo, 0, sizeof(gotinfo));

	if (doomcom->consoleplayer)
	{ // listen for setup info from key player
	//	printf ("listening for network start info...\n");
		while (1)
		{
			CheckAbort ();
			if (!HGetPacket ())
				continue;
			if (netbuffer->checksum & NCMD_SETUP)
			{
				if (netbuffer->player != VERSION)
					I_Error ("Different HERETIC versions cannot play a net game!");
				startskill = netbuffer->retransmitfrom & 15;
				deathmatch = (netbuffer->retransmitfrom & 0xc0) >> 6;
				nomonsters = (netbuffer->retransmitfrom & 0x20) > 0;
				respawnparm = (netbuffer->retransmitfrom & 0x10) > 0;
				//startmap = netbuffer->starttic & 0x3f;
				//startepisode = netbuffer->starttic >> 6;
				startmap = netbuffer->starttic & 15;
				startepisode = netbuffer->starttic >> 4;
				return;
			}
		}
	}
	else
 	{ // key player, send the setup info
	//	printf ("sending network start info...\n");
		do
		{
			CheckAbort ();
			for (i = 0; i < doomcom->numnodes; i++)
			{
				netbuffer->retransmitfrom = startskill;
				if (deathmatch)
					netbuffer->retransmitfrom |= (deathmatch << 6);
				if (nomonsters)
					netbuffer->retransmitfrom |= 0x20;
				if (respawnparm)
					netbuffer->retransmitfrom |= 0x10;
				//netbuffer->starttic = (startepisode * 64) + startmap;
				netbuffer->starttic = (startepisode << 4) + startmap;
				netbuffer->player = VERSION;
				netbuffer->numtics = 0;
				HSendPacket (i, NCMD_SETUP);
			}

#if 1
			for (i = 10; i && HGetPacket(); --i)
			{
				if ((netbuffer->player & 0x7f) < MAXNETNODES)
					gotinfo[netbuffer->player & 0x7f] = true;
			}
#else
			while (HGetPacket ())
			{
				gotinfo[netbuffer->player & 0x7f] = true;
			}
#endif

			for (i = 1; i < doomcom->numnodes; i++)
			{
				if (!gotinfo[i])
					break;
			}
		} while (i < doomcom->numnodes);
	}
}

/*
===================
=
= D_CheckNetGame
=
= Works out player numbers among the net participants
===================
*/

extern	int			viewangleoffset;

void D_CheckNetGame (void)
{
	int		i;

	for (i = 0; i < MAXNETNODES; i++)
	{
		nodeingame[i] = false;
		nettics[i] = 0;
		remoteresend[i] = false;	// set when local needs tics
		resendto[i] = 0;		// which tic to start sending
	}

// I_InitNetwork sets doomcom and netgame
	I_InitNetwork ();
	if (doomcom->id != DOOMCOM_ID)
		I_Error ("Doomcom buffer invalid!");
	netbuffer = &doomcom->data;
	consoleplayer = displayplayer = doomcom->consoleplayer;
	if (netgame)
		D_ArbitrateNetStart ();
//	printf ("startskill %i  deathmatch: %i  startmap: %i  startepisode: %i\n",
//		    startskill, deathmatch, startmap, startepisode);

// read values out of doomcom
	ticdup = doomcom->ticdup;
	maxsend = BACKUPTICS / (2*ticdup) - 1;
	if (maxsend < 1)
		maxsend = 1;

	for (i = 0; i < doomcom->numplayers; i++)
		playeringame[i] = true;
	for (i = 0; i < doomcom->numnodes; i++)
		nodeingame[i] = true;

//	printf ("player %i of %i (%i nodes)\n",
//		    consoleplayer + 1, doomcom->numplayers, doomcom->numnodes);
}

/*
==================
=
= D_QuitNetGame
=
= Called before quitting to leave a net game without hanging the
= other players
=
==================
*/

void D_QuitNetGame (void)
{
	int		i, j;

	if (debugfile)
		fclose (debugfile);

	if (!netgame || !usergame || consoleplayer == -1 || demoplayback)
		return;

// send a bunch of packets for security
	netbuffer->player = consoleplayer;
	netbuffer->numtics = 0;
	for (i = 0; i < 4; i++)
	{
		for (j = 1; j < doomcom->numnodes; j++)
		{
			if (nodeingame[j])
				HSendPacket (j, NCMD_EXIT);
		}
		I_WaitVBL (1);
	}
}


/*
===============
=
= TryRunTics
=
===============
*/

int	frametics[4], frameon;
int	frameskip[4];
int	oldnettics;
extern	boolean	advancedemo;

void TryRunTics (void)
{
	int		i;
	int		lowtic;
	int		entertic;
	static int	oldentertics;
	int		realtics, availabletics;
	int		counts;
	int		numplaying;

//
// get real tics
//
	entertic = I_GetTime () / ticdup;
	realtics = entertic - oldentertics;
	oldentertics = entertic;

//
// get available tics
//
	NetUpdate ();

	lowtic = H2MAXINT;
	numplaying = 0;
	for (i = 0; i < doomcom->numnodes; i++)
	{
		if (nodeingame[i])
		{
			numplaying++;
			if (nettics[i] < lowtic)
				lowtic = nettics[i];
		}
	}
	availabletics = lowtic - gametic/ticdup;

//
// decide how many tics to run
//
	if (realtics < availabletics - 1)
		counts = realtics + 1;
	else if (realtics < availabletics)
		counts = realtics;
	else
		counts = availabletics;
	if (counts < 1)
		counts = 1;

	frameon++;

	if (debugfile)
		fprintf (debugfile, "=======real: %i  avail: %i  game: %i\n", realtics, availabletics, counts);

	if (!demoplayback)
	{
	//	ideally nettics[0] should be 1 - 3 tics above lowtic
	//	if we are consistantly slower, speed up time
		for (i = 0; i < MAXPLAYERS; i++)
		{
			if (playeringame[i])
				break;
		}
		if (consoleplayer == i)
		{
			// the key player does not adapt
		}
		else
		{
			if (nettics[0] <= nettics[nodeforplayer[i]])
			{
				gametime--;
			//	printf ("-");
			}
			frameskip[frameon & 3] = (oldnettics > nettics[nodeforplayer[i]]);
			oldnettics = nettics[0];
			if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3])
			{
				skiptics = 1;
			//	printf ("+");
			}
		}
	}

	//
	// wait for new tics if needed
	//
	while (lowtic < gametic/ticdup + counts)
	{
		NetUpdate ();
		lowtic = H2MAXINT;

		for (i = 0; i < doomcom->numnodes; i++)
		{
			if (nodeingame[i] && nettics[i] < lowtic)
				lowtic = nettics[i];
		}

		if (lowtic < gametic / ticdup)
			I_Error ("TryRunTics: lowtic < gametic");

		// don't stay in here forever -- give the menu a chance to work
		if (I_GetTime() / ticdup - entertic >= 20)
		{
			MN_Ticker ();
			return;
		}
	}

//
// run the count * ticdup dics
//
	while (counts--)
	{
		for (i = 0; i < ticdup; i++)
		{
			if (gametic / ticdup > lowtic)
				I_Error ("gametic>lowtic");
			if (advancedemo)
				D_DoAdvanceDemo ();
			MN_Ticker ();
			G_Ticker ();
			gametic++;
			//
			// modify command for duplicated tics
			//
			if (i != ticdup - 1)
			{
				ticcmd_t	*cmd;
				int			buf;
				int			j;

				buf = (gametic / ticdup) % BACKUPTICS;
				for (j = 0; j < MAXPLAYERS; j++)
				{
					cmd = &netcmds[j][buf];
					cmd->chatchar = 0;
					if (cmd->buttons & BT_SPECIAL)
						cmd->buttons = 0;
				}
			}
		}
		NetUpdate ();	// check for new console commands
	}
}

